/********************************************
 * 功能说明: 
 * 模块名称: 
 * 系统名称: 
 * 软件版权: 北京银杉金服科技有限公司
 * 系统版本: 1.0.0
 * 开发人员: zhangfb
 * 开发时间: 2018/9/4 23:51
 * 审核人员: 
 * 相关文档: 
 * 修改记录: 修改日期 修改人员 修改说明
 *********************************************/
package com.hyacinth.yunpi.server.config;

import com.hyacinth.yunpi.server.security.CustomUserDetailsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.code.AuthorizationCodeServices;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import java.util.concurrent.TimeUnit;

/**
 * 授权服务器
 * @author zhangfb
 * @version 1.0.0.1
 * @since JDK 1.8
 */
@Configuration
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {

    /**
     * 配置授权服务一个比较重要的方面就是提供一个授权码给一个OAuth客户端{通过 authorization_code 授权类型}，
     * 一个授权码的获取是OAuth客户端跳转到一个授权页面，然后通过验证授权之后服务器重定向到OAuth客户端，
     * 并且在重定向连接中附带返回一个授权码。
     */

    private final AuthenticationManager authenticationManager;
    private final RedisConnectionFactory redisConnectionFactory;
    private final CustomUserDetailsService customUserDetailsService;


    @Autowired
    public AuthorizationServerConfig(CustomUserDetailsService customUserDetailsService, AuthenticationManager authenticationManager, RedisConnectionFactory redisConnectionFactory) {
        this.customUserDetailsService = customUserDetailsService;
        this.authenticationManager = authenticationManager;
        this.redisConnectionFactory = redisConnectionFactory;
    }

    // 声明TokenStore实现
    @Bean
    public TokenStore tokenStore() {
        return new RedisTokenStore(redisConnectionFactory);
    }

    /**
     * 配置授权以及令牌的访问端点和令牌服务。
     * @param endpoints 访问端点配置
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        // 认证管理器
        endpoints.authenticationManager(authenticationManager);
        endpoints.tokenStore(tokenStore());
        // 刷新令牌授权类型模式(refresh_token)的流程中包含一个检查，确保这个账号是否仍然有效
        endpoints.userDetailsService(customUserDetailsService);
        /// 这个属性是用来设置授权码服务，主要用于 "authorization_code" 授权码类型模式
        // endpoints.authorizationCodeServices();
        /// 这个属性就很牛B了，当你设置了这个东西（即TokenGranter接口实现），
        // 那么授权将会交由你来完全掌控，并且会忽略掉上面的这几个属性，这个属性一般是用作拓展用途的，
        // 即标准的四种授权模式已经满足不了你的需求的时候，才会考虑使用这个。
        // endpoints.tokenGranter();
        /// 配置授权端点的URL映射，第一个参数：String 类型的，这个端点URL的默认链接，第二个参数：String 类型的，你要进行替代的URL链接。
        // 参数都将以 "/" 字符为开始的字符串，框架的默认URL链接如下列表，可以作为这个 pathMapping() 方法的第一个参数：
        /*
         /oauth/authorize：授权端点。授权端点这个URL应该被Spring Security保护起来只供授权用户访问
         /oauth/token：令牌端点。默认是受保护的
         /oauth/confirm_access：用户确认授权提交端点。
         /oauth/error：授权服务错误信息端点。
         /oauth/check_token：用于资源服务访问的令牌解析端点。
         /oauth/token_key：提供公有密匙的端点，如果你使用JWT令牌的话。

         注意：如果你的应用程序中既包含授权服务又包含资源服务的话，
         那么这里实际上是另一个的低优先级的过滤器来控制资源接口的，
         这些接口是被保护在了一个访问令牌（access token）中，
         所以请挑选一个URL链接来确保你的资源接口中有一个不需要被保护的链接用来取得授权，
         就如上面示例中的 /login 链接，你需要在 WebSecurityConfigurer 配置对象中进行设置。
         */

        // endpoints.pathMapping();
        // 配置TokenServices参数
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(false);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        tokenServices.setAccessTokenValiditySeconds((int)TimeUnit.DAYS.toSeconds(30)); // 30天
        endpoints.tokenServices(tokenServices);
    }

    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束.
     * @param security 授权服务安全配置
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        security
                .tokenKeyAccess("permitAll()")
                .checkTokenAccess("isAuthenticated()");
    }


    /**
     * 客户端详情服务配置，
     * 客户端详情信息在这里进行初始化，
     * 把客户端详情信息写死在这里或者是通过数据库来存储调取详情信息。
     * @param clients 客户端详情服务配置
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
        clients.inMemory()
                .withClient("android")  //（必须的）用来标识客户的Id。
                .scopes("xx") // 用来限制客户端的访问范围，如果为空（默认）的话，那么客户端拥有全部的访问范围。
                .secret("android") // 需要值得信任的客户端）客户端安全码，如果有的话。
                .authorizedGrantTypes("password", "authorization_code", "refresh_token") //此客户端可以使用的授权类型，默认为空。
//                .authorities() //此客户端可以使用的权限（基于Spring Security authorities）。
                .and()
                .withClient("webapp")
                .scopes("xx")
                .authorizedGrantTypes("implicit");
    }
}
